package myutil

import (
	"bytes"
	"errors"
	"io/ioutil"
	"log"
	"os"
)

// Maxcap []byte max size
var Maxcap = 4096000

// IncludeMap all opened file info
type IncludeMap map[string]*IncludeFile

// IncludeFile remain include file info
type IncludeFile struct {
	Path    string
	File    string
	Counts  uint
	Parent  *IncludeFile
	Line    uint
	Err     error
	Buf     []byte
	Output  []byte
	Offsize int
	Imap    IncludeMap
	Delim   []byte
	Status  StatIndex
	NumSpc  int
}

// StatIndex type of index delemit
type StatIndex uint

// NotRead file not do
const (
	NotRead StatIndex = iota
	Match
	NotMatch
	OnRead
	End
	Done
)

// Parser read the first file
func Parser(fileName, delim string) []byte {
	var output []byte
	// set max size to avoid recursive loop
	output = make([]byte, 0, Maxcap)
	fileMap := make(IncludeMap, 100)
	iFile := &IncludeFile{File: fileName, Counts: 0, Parent: nil, Buf: output, Output: output, Imap: fileMap, Delim: []byte(delim), Status: NotRead}
	i := iFile.addMap()

	for {
		if i.Status == Done {
			break
		}

		i = i.doSplitBuf()
	}

	return i.Output
}

// parseFile parse file
func (i *IncludeFile) parseFile() {
	err := i.readConf()
	if err != nil {
		log.Fatalf("error : %v", err)
		os.Exit(1)
	}

	i.Status = NotRead

	return
}

// doSplitBuf do delim buf
func (i *IncludeFile) doSplitBuf() *IncludeFile {
	switch i.Status {
	case End:
		if i.Parent != nil {
			i.Parent.Output = i.Output
			return i.Parent.splitBuf()
		}
		i.Status = Done
		return i
	case NotRead:
		i.parseFile()
		if i.NumSpc > 0 {
			i.addSpace()
		}
		return i.splitBuf()
	case NotMatch:
		return i.splitBuf()
	case Match:
		return i.splitBuf()
	}

	i.Status = Done
	return i
}

// addMap add to IncludeMap if no exist
func (i *IncludeFile) addMap() *IncludeFile {
	imap := i.Imap

	if v, ok := imap[i.File]; ok {
		if v.Status == OnRead {
			log.Fatalf("error : %v", errors.New("include recursive"))
			os.Exit(1)
		}
		return v
	}

	imap[i.File] = &IncludeFile{Path: i.Path, File: i.File, Counts: 0, Parent: i.Parent, Line: i.Line, Err: i.Err, Buf: i.Buf, Output: i.Output, Offsize: i.Offsize, Imap: imap, Delim: i.Delim, Status: i.Status, NumSpc: i.NumSpc}
	return imap[i.File]
}

// splitBuf check delimit is true
func (i *IncludeFile) splitBuf() *IncludeFile {

	m := bytes.Index(i.Buf, i.Delim)
	if m < 0 {
		i.Output = append(i.Output, i.Buf...)
		i.Status = End

		return i
	}

	i.Offsize = m
	p, ok1 := i.prevLine(i.Buf[:m])
	q, n, ok2 := i.nextLine(i.Buf[m:])

	if ok1 && ok2 {
		i.Output = append(i.Output, i.Buf[:p]...)
		i.Buf = i.Buf[m:]
		iFileName := string(bytes.TrimSpace(i.Buf[q:n]))
		i.Buf = i.Buf[n:]
		i.Status = OnRead
		iFile := &IncludeFile{File: iFileName, Parent: i, Output: i.Output, Imap: i.Imap, Delim: []byte(i.Delim), NumSpc: i.NumSpc}
		i1 := iFile.addMap()

		i.Status = Match

		return i1
	}

	i.Output = append(i.Output, i.Buf[:m+n]...)
	i.Buf = i.Buf[m+n:]
	i.Status = NotMatch

	return i
}

// prevLine  get previous line end position,normal is '\n'
func (i *IncludeFile) prevLine(prevBuf []byte) (int, bool) {
	for j := i.Offsize; j >= 0; j-- {
		if prevBuf[j-1] == ' ' || prevBuf[j-1] == '\t' {
			continue
		}
		if prevBuf[j-1] == '\n' {
			i.NumSpc = i.Offsize - j
			return j, true
		}
		return i.Offsize, false
	}
	return 0, true
}

// nextLine  get next line end position,normal is '\n' next character
func (i *IncludeFile) nextLine(nextBuf []byte) (int, int, bool) {
	l := len(nextBuf)
	var n int

	if j := bytes.IndexByte(nextBuf, byte('\n')); j >= 0 {
		n = j
	} else {
		n = l
	}
	for s := len(i.Delim); s < n; s++ {
		if nextBuf[s] == ' ' || nextBuf[s] == '\t' {
			continue
		}
		if nextBuf[s] == ':' {
			return s + 1, n, true
		}
		return 0, n, false
	}

	return 0, n, true
}

func (i *IncludeFile) addSpace() {
	var tmp, tLine, tBuf []byte
	var m int
	tLine = make([]byte, i.NumSpc)
	for j := 0; j < i.NumSpc; j++ {
		tLine[j] = ' '
	}
	tmp = make([]byte, 0)
	tBuf = make([]byte, len(i.Buf))
	copy(tBuf, i.Buf)

	for {
		if len(tBuf) > 0 {
			tmp = append(tmp, tLine...)
		}

		m = bytes.IndexByte(tBuf, byte('\n'))
		if m >= 0 {
			tmp = append(tmp, tBuf[:m+1]...)
			tBuf = tBuf[m+1:]
			continue
		}
		if len(tBuf) < 1 {
			break
		}

		tmp = append(tmp, tBuf...)
		tBuf = nil
	}

	i.Buf = tmp
}

// openFileWithCounter ss
func (i *IncludeFile) readConf() error {
	f, err := os.Open(i.File)

	if err != nil {
		return err
	}

	defer func() {
		f.Close()
	}()

	r, err2 := ioutil.ReadAll(f)

	if err2 != nil {
		return err2
	}
	i.Buf = r

	return nil
}

/*
func (e *IncludeError) Error() string { return e.Path + e.File + " : " + e.Line + ": " + e.Err.Error() }
*/
